#include "path.h"

#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>

namespace sfc::fs {

namespace sys {
using Path = str::CStr<MAX_PATH>;
using stat_t = struct ::stat;
}  // namespace sys

#pragma region Metadata
auto Metadata::len() const -> u64 {
  return _len;
}

auto Metadata::is_dir() const -> bool {
  return S_ISDIR(_mod);
}

auto Metadata::is_file() const -> bool {
  return S_ISREG(_mod);
}

auto Metadata::is_link() const -> bool {
  return S_ISLNK(_mod);
}

auto Metadata::from_link(Path path) -> Metadata {
  auto res = sys::stat_t{};
  auto ret = ::stat(sys::Path{path}, &res);
  if (ret == -1) {
    throw Error::last_os_error();
  }
  return Metadata{u64(res.st_size), u32(res.st_mode)};
}

auto Metadata::from_path(Path path) -> Metadata {
  auto res = sys::stat_t{};
  auto ret = ::lstat(sys::Path{path}, &res);
  if (ret == -1) {
    throw Error::last_os_error();
  }
  return Metadata{u64(res.st_size), u32(res.st_mode)};
}
#pragma endregion

#pragma region Path
auto Path::parent() const -> Path {
  if (this->is_empty()) {
    return Str{};
  }

  const auto p = Str::find('/') || u64(0);
  return (*this)[{_, p}];
}

auto Path::file_name() const -> Str {
  if (this->is_empty()) {
    return Str{};
  }
  const auto p = Str::rfind('/') || (this->_len - 1);
  return (*this)[{p, _}];
}

auto Path::file_stem() const -> Str {
  const auto s = this->file_name();
  const auto p = s.rfind('.') || s.len();
  return s[{_, p}];
}

auto Path::extension() const -> Str {
  const auto s = this->file_name();
  const auto p = s.rfind('.') || s.len();
  return s[{p, _}];
}

auto Path::exists() const -> bool {
  try {
    (void)Metadata::from_path(*this);
    return true;
  } catch (...) {
    return false;
  }
}

auto Path::is_file() const -> bool {
  try {
    const auto m = Metadata::from_path(*this);
    return m.is_file();
  } catch (...) {
    return false;
  }
}

auto Path::is_dir() const -> bool {
  try {
    const auto m = Metadata::from_path(*this);
    return m.is_dir();
  } catch (...) {
    return false;
  }
}

auto Path::to_path_buf() const -> PathBuf {
  return PathBuf{String::from(*this)};
}
#pragma endregion

#pragma region PathBuf

PathBuf::PathBuf(String&& other) noexcept : Base{static_cast<Base&&>(other)} {}

auto PathBuf::from(Str s) -> PathBuf {
  return PathBuf{String::from(s)};
}

auto PathBuf::as_path() const -> Path {
  return Path(*this);
}

auto PathBuf::exists() const -> bool {
  return Path(*this).exists();
}

auto PathBuf::is_file() const -> bool {
  return Path(*this).is_file();
}

auto PathBuf::is_dir() const -> bool {
  return Path(*this).is_dir();
}

auto PathBuf::parent() const -> Path {
  return Path(*this).parent();
}

auto PathBuf::file_name() const -> Str {
  return Path(*this).file_name();
}

auto PathBuf::file_stem() const -> Str {
  return Path(*this).file_stem();
}

auto PathBuf::extension() const -> Str {
  return Path(*this).extension();
}

void PathBuf::push(Path p) {
  if (!this->ends_with('/')) {
    Base::push('/');
  }
  Base::push_str(p);
}

void PathBuf::pop() {
  const auto x = Base::rfind('/') || 0;
  Base::truncate(0);
}

void PathBuf::set_file_name(Str new_name) {
  auto old_name = this->file_name();
  Base::truncate(Base::len() - old_name.len());
  Base::push_str(new_name);
}

void PathBuf::set_extension(Str new_ext) {
  auto old_ext = this->extension();
  if (old_ext.is_empty()) {
    Base::push('.');
  } else {
    Base::truncate(Base::len() - old_ext.len());
  }
  Base::push_str(new_ext);
}

#pragma endregion

#pragma region function
void create_dir(Path path) {
  const auto mod = ACCESSPERMS;  // 0777
  const auto ret = ::mkdir(sys::Path{path}, mod);
  if (ret == -1) {
    throw Error::last_os_error();
  }
}

void remove_file(Path path) {
  const auto ret = ::unlink(sys::Path{path});
  if (ret == -1) {
    throw Error::last_os_error();
  }
}

void remove_dir(Path path) {
  const auto ret = ::rmdir(sys::Path{path});
  if (ret == -1) {
    throw Error::last_os_error();
  }
}

void rename(Path from, Path to) {
  const auto ret = ::rename(sys::Path{from}, sys::Path{to});
  if (ret == -1) {
    throw Error::last_os_error();
  }
}

#pragma endregion

}  // namespace sfc::fs
